﻿namespace MailChimp

open System
open MailChimp.Types

//==================================================================================
//  MandrillApi class
//==================================================================================

[<Sealed>]
type MandrillApi(apiKey: string) =
    inherit ApiBase(apiKey, true)

    override x.UserAgent = "MANDRILL.NET/1.0"
    override x.ApiUrl = "https://mandrillapp.com/api/1.0/{method}.{fmt}"
    override x.OutputFormat = Format.xml
    override x.KeyParam = "key"


module MandrillHelper =

    let getError x =
        let e =
            try prepareXml x |> deserializeXml<Mandrill.Error>
            with | _ -> Mandrill.Error()
        raise (MCException(e, null, x))

    let deserialize<'T> x =
        try deserializeXml<'T> x
        with | ex -> deserializeFail ex x

    let prepare x =
        try prepareXml x
        with | ex -> transformFail ex x


open MandrillHelper

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MandrillApi =

    open MailChimp.Types.Mandrill

    let callServerRaw (x: MandrillApi) name p =
        let st, d = x.CallServer(WebMethod.Post, name, None, p)
        match st with
        | httpst.OK -> d
        | httpst.InternalServerError -> getError d
        | c -> callServerFail null (int c) d

    let callServer<'T> x name p =
        callServerRaw x name p |> prepare |> deserialize<'T>

    //------------------------------------------------------------------------------
    //  (*) Helper methods
    //------------------------------------------------------------------------------

    let private timeSeries<'T> (x: MandrillApi) section param (value: string opt) =
        let p = Input()
        p.[param] <- value
        let n = match value with
                | None -> section + "/all-time-series"
                | _ ->    section +     "/time-series"
        callServer<MVList<'T>> x n (Some p)

    //------------------------------------------------------------------------------
    //  Users related methods
    //------------------------------------------------------------------------------

    module Users =

        let ping (x: MandrillApi) = callServer<string> x "users/ping" None

        let info (x: MandrillApi) = callServer<UserInfo> x "users/info" None

        let senders (x: MandrillApi) = callServer<MVList<SenderInfo>> x "users/senders" None

    //------------------------------------------------------------------------------
    //  Messages related methods
    //------------------------------------------------------------------------------

    module Messages =

        open MailChimp.Types.Mandrill.Messages

        let send (x: MandrillApi) (message: Message) (async: bool opt) =
            let p = Input()
            p.["message"] <- message
            p.["async"] <- async
            callServer<MVList<SendResult>> x "messages/send" (Some p)

        let sendTemplate (x: MandrillApi) (templateName: string)
            (templateContent: NameContentList<string>) (message: Message) (async: bool opt) =
            let p = Input()
            p.["template_name"] <- templateName
            p.["template_content"] <- templateContent
            p.["message"] <- message
            p.["async"] <- async
            callServer<MVList<SendResult>> x "messages/send-template" (Some p)

        let sendRaw (x: MandrillApi) (message: string) (fromEmail: string opt)
            (fromName: string opt) (toEmails: string seq opt) (async: bool opt) =
            let p = Input()
            p.["raw_message"] <- message
            p.["from_email"] <- fromEmail
            p.["from_name"] <- fromName
            p.["to"] <- toEmails
            p.["async"] <- async
            callServer<MVList<SendResult>> x "messages/send-raw" (Some p)

        let parse (x: MandrillApi) (message: string) =
            let p = Input()
            p.["raw_message"] <- message
            callServer<ParsedMessage> x "messages/parse" (Some p)

        let search (x: MandrillApi) (query: string) (dateFrom: DateTime opt) (dateTo: DateTime opt)
            (tags: string seq opt) (senders: string seq opt) (limit: int opt) =
            let p = Input()
            p.["query"] <- query
            p.["date_from"] <- dateFrom
            p.["date_to"] <- dateTo
            p.["tags"] <- tags
            p.["senders"] <- senders
            p.["limit"] <- limit
            callServer<MVList<SearchResult>> x "messages/search" (Some p)

    //------------------------------------------------------------------------------
    //  Tags related methods
    //------------------------------------------------------------------------------

    module Tags =

        let list (x: MandrillApi) =
            callServer<MVList<TagInfo>> x "tags/list" None

        let info (x: MandrillApi) (tag: string) =
            let p = Input()
            p.["tag"] <- tag
            callServer<TagInfo> x "tags/info" (Some p)

        let timeSeries x tag = timeSeries<Stats> x "tags" "tag" tag

    //------------------------------------------------------------------------------
    //  Senders related methods
    //------------------------------------------------------------------------------

    module Senders =

        let list (x: MandrillApi) =
            callServer<MVList<SenderInfo>> x "senders/list" None

        let domains (x: MandrillApi) =
            callServer<MVList<Domain>> x "senders/domains" None

        let info (x: MandrillApi) (address: string) =
            let p = Input()
            p.["address"] <- address
            callServer<SenderInfo> x "senders/info" (Some p)

        let timeSeries x address = timeSeries<Stats> x "senders" "address" (Some address)

    //------------------------------------------------------------------------------
    //  Blacklist (rejects) related methods
    //------------------------------------------------------------------------------

    module Blacklist =

        open Rejections

        let list (x: MandrillApi) (email: string opt) (includeExpired: bool opt) =
            let p = Input()
            p.["email"] <- email
            p.["include_expired"] <- includeExpired
            callServer<MVList<BlacklistItem>> x "rejects/list" (Some p)

        let private op<'T> (x: MandrillApi) op (email: string) =
            let p = Input()
            let n = "rejects/" + op
            p.["email"] <- email
            callServer<'T> x n (Some p)

        let add x email =
            let r = op<ListAddResult> x "add" email in r.Added

        let delete x email =
            let r = op<ListDelResult> x "delete" email in r.Deleted

    //------------------------------------------------------------------------------
    //  Whitelist related methods
    //------------------------------------------------------------------------------

    module Whitelist =

        open Rejections

        let list (x: MandrillApi) (email: string opt) =
            let p = Input()
            p.["email"] <- email
            callServer<MVList<WhitelistItem>> x "whitelists/list" (Some p)

        let private op<'T> (x: MandrillApi) op (email: string) =
            let p = Input()
            let n = "whitelists/" + op
            p.["email"] <- email
            callServer<'T> x n (Some p)

        let add x email =
            let r = op<ListAddResult> x "add" email in r.Added

        let delete x email =
            let r = op<ListDelResult> x "delete" email in r.Deleted

    //------------------------------------------------------------------------------
    //  Urls related methods
    //------------------------------------------------------------------------------

    module Urls =

        let list (x: MandrillApi) =
            callServer<MVList<UrlInfo>> x "urls/list" None

        let search (x: MandrillApi) (query: string) =
            let p = Input()
            p.["q"] <- query
            callServer<MVList<UrlInfo>> x "urls/search" (Some p)

        let timeSeries x url = timeSeries<UrlStats> x "urls" "url" (Some url)

    //------------------------------------------------------------------------------
    //  Templates related methods
    //------------------------------------------------------------------------------

    module Templates =

        let list (x: MandrillApi) =
            callServer<MVList<TemplateInfo>> x "templates/list" None

        let info (x: MandrillApi) (name: string) =
            let p = Input()
            p.["name"] <- name
            callServer<TemplateInfo> x "templates/info" (Some p)

        let private op (x: MandrillApi) op (name: string) (code: string opt) (publish: bool opt) =
            let p = Input()
            let n = "templates/" + op
            p.["name"] <- name
            p.["code"] <- code
            p.["publish"] <- publish
            callServer<TemplateInfo> x n (Some p)

        let add x name code publish = op x "add" name (Some code) publish
        let update x name code publish = op x "update" name (Some code) publish
        let publish x name = op x "publish" name None None
        let delete x name = op x "delete" name None None

        let render (x: MandrillApi) (templateName: string)
            (templateContent: NameContentList<string>) (mergeVars: NameContentList<string> opt) =
            let p = Input()
            p.["template_name"] <- templateName
            p.["template_content"] <- templateContent
            p.["merge_vars"] <- mergeVars
            let x = callServer<RenderResult> x "templates/render" (Some p)
            x.Html

        let timeSeries x name = timeSeries<Stats> x "templates" "name" (Some name)

    //------------------------------------------------------------------------------
    //  Webhooks related methods
    //------------------------------------------------------------------------------

    module Webhooks =

        let list (x: MandrillApi) =
            callServer<MVList<WebhookInfo>> x "webhooks/list" None

        let info (x: MandrillApi) (id: int) =
            let p = Input()
            p.["id"] <- id
            callServer<WebhookInfo> x "webhooks/info" (Some p)

        let private op (x: MandrillApi) op (id: int opt) (url: string opt)
            (description: string opt) (events: EventType seq opt) =
            let p = Input()
            let n = "webhooks/" + op
            p.["id"] <- id
            p.["url"] <- url
            p.["description"] <- description
            p.["events"] <- events
            callServer<WebhookInfo> x n (Some p)

        let add x url description events = op x "add" None (Some url) description events
        let delete x id = op x "delete" (Some id) None None None
        let update x id url description events =
            op x "update" (Some id) (Some url) description events

    //------------------------------------------------------------------------------
    //  Inbound related methods
    //------------------------------------------------------------------------------

    module Inbound =

        open MailChimp.Types.Mandrill.Inbound

        let domains (x: MandrillApi) =
            callServer<MVList<Domain>> x "inbound/domains" None

        let routes (x: MandrillApi) (domain: string) =
            let p = Input()
            p.["domain"] <- domain
            callServer<MVList<Route>> x "inbound/routes" (Some p)

        let sendRaw (x: MandrillApi) (message: string) (toEmails: string seq opt)
            (mailFrom: string opt) (helo: string opt) (clientAddress: string opt) =
            let p = Input()
            p.["raw_message"] <- message
            p.["to"] <- toEmails
            p.["mail_from"] <- mailFrom
            p.["helo"] <- helo
            p.["client_address"] <- clientAddress
            callServer<MVList<Recipient>> x "inbound/send-raw" (Some p)

//==================================================================================
//  MandrillApi augmentation
//==================================================================================

module MA = MandrillApi

type MandrillApi with

    //------------------------------------------------------------------------------
    //  (*) Custom API call
    //------------------------------------------------------------------------------

    member x.CallServer<'T>(methodName, [<op>]p) = MA.callServer<'T> x methodName p

    //------------------------------------------------------------------------------
    //  Users related methods
    //------------------------------------------------------------------------------

    member x.Ping() = MA.Users.ping x
    member x.UserInfo() = MA.Users.info x
    member x.Senders() = MA.Users.senders x

    //------------------------------------------------------------------------------
    //  Messages related methods
    //------------------------------------------------------------------------------

    member x.Send(message, [<op>]async) = MA.Messages.send x message async
    member x.Send(message, ?async) = MA.Messages.send x message ((<?)async)
    member x.SendTemplate(templateName, templateContent, message, [<op>]async) =
        MA.Messages.sendTemplate x templateName templateContent message async
    member x.SendTemplate(templateName, templateContent, message, ?async) =
        MA.Messages.sendTemplate x templateName templateContent message ((<?)async)
    member x.SendRaw(message, [<op>]fromEmail, [<op>]fromName, [<op>]toEmails, [<op>]async) =
        MA.Messages.sendRaw x message fromEmail fromName toEmails async
    member x.SendRaw(message, ?fromEmail, ?fromName, ?toEmails, ?async) =
        MA.Messages.sendRaw x message ((<?)fromEmail) ((<?)fromName) ((<?)toEmails) ((<?)async)
    member x.Parse(message) = MA.Messages.parse x message
    member x.MessagesSearch(query, [<op>]dateFrom, [<op>]dateTo, [<op>]tags, [<op>]senders, [<op>]limit) =
        MA.Messages.search x query dateFrom dateTo tags senders limit
    member x.MessagesSearch(query, ?dateFrom, ?dateTo, ?tags, ?senders, ?limit) =
        MA.Messages.search x query ((<?)dateFrom) ((<?)dateTo) ((<?)tags) ((<?)senders) ((<?)limit)

    //------------------------------------------------------------------------------
    //  Tags related methods
    //------------------------------------------------------------------------------

    member x.TagsList() = MA.Tags.list x
    member x.TagInfo(tag) = MA.Tags.info x tag
    member x.TagsTimeSeries([<op>]tag) = MA.Tags.timeSeries x tag
    member x.TagsTimeSeries(?tag) = MA.Tags.timeSeries x ((<?)tag)

    //------------------------------------------------------------------------------
    //  Senders related methods
    //------------------------------------------------------------------------------

    member x.SendersList() = MA.Senders.list x
    member x.SendersDomains() = MA.Senders.domains x
    member x.SenderInfo(address) = MA.Senders.info x address
    member x.SendersTimeSeries(address) = MA.Senders.timeSeries x address

    //------------------------------------------------------------------------------
    //  Blacklist related methods
    //------------------------------------------------------------------------------

    member x.Blacklist([<op>]email, [<op>]includeExpired) = MA.Blacklist.list x email includeExpired
    member x.Blacklist(?email, ?includeExpired) = MA.Blacklist.list x ((<?)email) ((<?)includeExpired)
    member x.BlacklistAdd(email) = MA.Blacklist.add x email
    member x.BlacklistDel(email) = MA.Blacklist.delete x email

    //------------------------------------------------------------------------------
    //  Whitelist related methods
    //------------------------------------------------------------------------------

    member x.Whitelist([<op>]email) = MA.Whitelist.list x email
    member x.Whitelist(?email) = MA.Whitelist.list x ((<?)email)
    member x.WhitelistAdd(email) = MA.Whitelist.add x email
    member x.WhitelistDel(email) = MA.Whitelist.delete x email

    //------------------------------------------------------------------------------
    //  Urls related methods
    //------------------------------------------------------------------------------

    member x.UrlsList() = MA.Urls.list x
    member x.UrlsSearch(query) = MA.Urls.search x query
    member x.UrlsTimeSeries(url) = MA.Urls.timeSeries x url

    //------------------------------------------------------------------------------
    //  Templates related methods
    //------------------------------------------------------------------------------

    member x.TemplatesList() = MA.Templates.list x
    member x.TemplateInfo(name) = MA.Templates.info x name
    member x.TemplateAdd(name, code, [<op>]publish) = MA.Templates.add x name code publish
    member x.TemplateAdd(name, code, ?publish) = MA.Templates.add x name code ((<?)publish)
    member x.TemplateUpdate(name, code, [<op>]publish) = MA.Templates.update x name code publish
    member x.TemplateUpdate(name, code, ?publish) = MA.Templates.update x name code ((<?)publish)
    member x.TemplatePublish(name) = MA.Templates.publish x name
    member x.TemplateDel(name) = MA.Templates.delete x name
    member x.TemplateRender(templateName, templateContent, [<op>]mergeVars) =
        MA.Templates.render x templateName templateContent mergeVars
    member x.TemplateRender(templateName, templateContent, ?mergeVars) =
        MA.Templates.render x templateName templateContent ((<?)mergeVars)
    member x.TemplatesTimeSeries(name) = MA.Senders.timeSeries x name

    //------------------------------------------------------------------------------
    //  Webhooks related methods
    //------------------------------------------------------------------------------

    member x.WebhooksList() = MA.Webhooks.list x
    member x.WebhookInfo(id) = MA.Webhooks.info x id
    member x.WebhookAdd(url, [<op>]description, [<op>]events) =
        MA.Webhooks.add x url description events
    member x.WebhookAdd(url, ?description, ?events) =
        MA.Webhooks.add x url ((<?)description) ((<?)events)
    member x.WebhookUpdate(id, url, [<op>]description, [<op>]events) =
        MA.Webhooks.update x id url description events
    member x.WebhookUpdate(id, url, ?description, ?events) =
        MA.Webhooks.update x id url ((<?)description) ((<?)events)
    member x.WebhookDel(id) = MA.Webhooks.delete x id

    //------------------------------------------------------------------------------
    //  Inbound related methods
    //------------------------------------------------------------------------------

    member x.InboundDomains() = MA.Inbound.domains x
    member x.InboundRoutes(domain) = MA.Inbound.routes x domain
    member x.InboundSendRaw(message, [<op>]toEmails, [<op>]mailFrom, [<op>]helo, [<op>]clientAddress) =
        MA.Inbound.sendRaw x message toEmails mailFrom helo clientAddress
    member x.InboundSendRaw(message, ?toEmails, ?mailFrom, ?helo, ?clientAddress) =
        MA.Inbound.sendRaw x message ((<?)toEmails) ((<?)mailFrom) ((<?)helo) ((<?)clientAddress)